package com.bolt.support.sqlconfig.builder;

import com.bolt.common.convert.Convert;
import com.bolt.common.convert.impl.CollectionConverter;
import com.bolt.common.ognl.OgnlCache;
import com.bolt.common.utils.DateUtil;
import com.bolt.common.utils.StrUtil;
import com.bolt.common.xml.GenericTokenParser;
import com.bolt.common.xml.TokenHandler;
import com.bolt.convention.exception.ServiceException;
import com.bolt.support.sqlconfig.base.SqlHelper;
import com.bolt.support.sqlconfig.base.SqlText;
import com.bolt.support.sqlconfig.base.TypeConvertHandler;

import java.util.*;


@SuppressWarnings("rawtypes")
public class SqlSourceBuilder {

    public SqlText parse(String originalSql, String parameterType, Object parameterObject) {
        ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(parameterType, parameterObject);
        GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);
        String sql = parser.parse(originalSql);
        sql = SqlHelper.getLineSql(sql);
        return new SqlText(sql, handler.getParameterValues());
    }

    private static class NumberConvertHandler implements TypeConvertHandler {

        private Class type;

        public NumberConvertHandler(Class type) {
            this.type = type;
        }

        @Override
        @SuppressWarnings("unchecked")
        public Object convert(Object value) {
            if(value instanceof Collection){
               return new CollectionConverter(List.class,type).convert(value,null);
            }else{
                return Convert.convert(type, value);
            }
        }
    }

    private static class ListConvertHandler implements TypeConvertHandler {

        @Override
        public Object convert(Object sqlValue) {
            if (String.class.isAssignableFrom(sqlValue.getClass())) {
                String[] arrayValue = ((String) sqlValue).split(",");
                return Arrays.asList(arrayValue);
            }
            return sqlValue;
        }
    }

    private static class DateConvertHandler implements TypeConvertHandler {
        @Override
        public Object convert(Object sqlValue) {
            return DateUtil.parseDate((String) sqlValue);
        }
    }

    private static class ParameterMappingTokenHandler implements TokenHandler {
        private Properties parameterValues;

        private String parameterType;
        private Object parameterObject;

        public ParameterMappingTokenHandler(String parameterType,
                                            Object parameterObject) {
            parameterValues = new Properties();
            this.parameterType = parameterType;
            this.parameterObject = parameterObject;
        }

        public Properties getParameterValues() {
            return parameterValues;
        }

        @Override
        public String handleToken(String content) {
            StringTokenizer parameterMappingParts = new StringTokenizer(
                    content, ", \n\r\t");
            String propertyWithJdbcType = parameterMappingParts.nextToken();
            String expression = extractPropertyName(propertyWithJdbcType);
            String property = removeExpression(expression);
            String jdbcType = extractJdbcType(propertyWithJdbcType);
            parameterValues.put(property, buildParameter(expression, jdbcType));
            return ":" + property;
        }

        private String removeExpression(String expression) {
            int index = StrUtil.indexOf(expression, '[');
            if (index > -1) {
                String subscript = StrUtil.sub(expression,index+1,index+2);
                return StrUtil.subPre(expression, index)+subscript;
            } else {
                return expression;
            }
        }

        public Object buildParameter(String property, String jdbcType) {
            jdbcType = jdbcType == null ? parameterType : jdbcType;
            Object sqlValue = null;
            try {
                sqlValue = OgnlCache.getValue(property, parameterObject);
            } catch (Exception e) {
                throw new ServiceException(
                        "Error evaluating expression '" + property
                                + "'. Cause: " + e, e);
            }
            if (!(parameterObject instanceof Map)) {
                return sqlValue;
            }
            if ("Integer".equals(jdbcType)) {
                final NumberConvertHandler handler = new NumberConvertHandler(
                        Integer.class);
                return handler.convert(sqlValue);
            } else if ("Long".equals(jdbcType)) {
                final NumberConvertHandler handler = new NumberConvertHandler(
                        Long.class);
                return handler.convert(sqlValue);
            } else if ("Float".equals(jdbcType)) {
                final NumberConvertHandler handler = new NumberConvertHandler(
                        Float.class);
                return handler.convert(sqlValue);
            } else if ("Double".equals(jdbcType)) {
                final NumberConvertHandler handler = new NumberConvertHandler(
                        Double.class);
                return handler.convert(sqlValue);
            } else if ("List".equals(jdbcType)) {
                final ListConvertHandler handler = new ListConvertHandler();
                return handler.convert(sqlValue);
            } else if ("Date".equals(jdbcType)) {
                final DateConvertHandler handler = new DateConvertHandler();
                return handler.convert(sqlValue);
            } else {
                return sqlValue;
            }
        }

        private String extractPropertyName(String property) {
            if (property.contains(":")) {
                StringTokenizer simpleJdbcTypeParser = new StringTokenizer(
                        property, ": ");
                if (simpleJdbcTypeParser.countTokens() == 2) {
                    return simpleJdbcTypeParser.nextToken();
                }
            }

            return property;
        }

        private String extractJdbcType(String property) {
            if (property.contains(":")) {
                StringTokenizer simpleJdbcTypeParser = new StringTokenizer(property, ": ");
                if (simpleJdbcTypeParser.countTokens() == 2) {
                    simpleJdbcTypeParser.nextToken();
                    return simpleJdbcTypeParser.nextToken().trim();
                }
            }
            return null;
        }
    }


}
